package com.lenovo.leos.filebrowser.engine.util;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Bitmap.Config;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.MediaStore.Images;
import android.util.Log;
import android.widget.ImageView;

import com.lenovo.leos.filebrowser.engine.model.IconifiedText;
import com.lenovo.leos.filebrowser.engine.util.FileThread;
import com.lenovo.leos.filebrowser.engine.util.FileUtil;
import com.lenovo.leos.filebrowser.R;


public class ThumbnailService extends FileThread {
	private static final String TAG = "ThumbnailService";
	
	private static final int CACHE_CAPACITY = 70;
	private static final int THUMBNAIL_WIDTH = 90;
	private static final int THUMBNAIL_HEIGHT = 90;
	private static final String[] PROJ_IMAGE = new String[] {BaseColumns._ID, MediaStore.Images.Thumbnails.DATA };
	private static final String[] PROJ_VIDEO = new String[] {BaseColumns._ID, MediaStore.MediaColumns.DATA};
	
	private ThumbnailService(Context context, Handler handler) {
		super(context, handler);
		// TODO Auto-generated constructor stub		
	}

	private ThumbnailService(Context context) {
		this(context, null);
		// TODO Auto-generated constructor stub
	}
	
	private static ThumbnailService sInstance = null;
	private static Handler mhdSendMsg = null;
	private static boolean mbHaveNewTasks = false;	
	private static BitmapFactory.Options options = new BitmapFactory.Options();
	private static List<IconifiedText> mlitNewTask = null;
	private static LinkedList<IconifiedText> mllFailedTasks = new LinkedList<IconifiedText>();	
	private static LinkedList<IconifiedText> mllTaskStack = new LinkedList<IconifiedText>();
	private static HashMap<File, Drawable> mmapCache = new LinkedHashMap<File, Drawable>(CACHE_CAPACITY + 1, 0.75F, false) {
		protected boolean removeEldestEntry(Map.Entry<File, Drawable> eldest) {
			return size() > CACHE_CAPACITY;
		}
	};
		
	/*
	 * public API
	 */
	public static boolean needThumbnailOrNot(Context context, IconifiedText it) {
		if (null == it || null == it.getFile() || !it.getFile().exists()){
			Log.i(TAG, "it is invalid in needThumbnailOrNot");
			return false;
		}else{
			String mimetype = FileUtil.getMimeTypeOfFile(context, it.getFile());
			if (mimetype.startsWith("image")||
					mimetype.startsWith("video") ||
					mimetype.startsWith("folder")){
				return true;
			}else{
				return false;
			}
		}
	}
	
	public static void setHandler(Handler handler) {
		mhdSendMsg = handler;
	}
		
	public synchronized static void getThumbNailForFile(IconifiedText it){
		Log.i(TAG, "==> getThumbNailForFile");
		
		if (null == it)
			return;
		else {
			pushTask(it);
		}
	}
	
	public synchronized static void getThumbNailForFiles(List<IconifiedText> lit){
		Log.i(TAG, "==> getThumbNailForFiles");
		
		if (null == lit || lit.isEmpty()) {
			Log.e(TAG, "invalid lit in getThumbnailForFiles");
			return;				
		} else {
			if (null != mlitNewTask)
				mlitNewTask.clear();
			
			mlitNewTask = (List<IconifiedText>) ((ArrayList<IconifiedText>)lit).clone();
			mbHaveNewTasks = true;			
		}		
	}
	
	public static boolean isReady(){
		if (null == sInstance || !sInstance.isAlive())
			return false;
		else
			return true;
	}
	
	public synchronized static void initialize(Context context) {
		if (!isReady())
			getInstance(context,null);
	}
	
	public synchronized static void release(){
		if (isReady())
			releaseInstance();		
	}
	
	/*
	 * private functions
	 */
	private synchronized static ThumbnailService getInstance (Context context, Handler handler) {		
		if (null == sInstance || !sInstance.isAlive()){
			Log.i(TAG, "new ThumbNailThread");
			sInstance = new ThumbnailService(context, handler);
			sInstance.start();			
		}		
		sInstance.setHandler(handler);
		return sInstance;
	}
	
	private synchronized static void releaseInstance() {
		Log.i(TAG, "==> releaseInstance");
		if(null != sInstance && sInstance.isAlive())
			sInstance.stopIt();
	}
	
	private synchronized static void pushTask (IconifiedText it) {
		if (null == mllTaskStack)
			mllTaskStack = new LinkedList<IconifiedText>();
		else
			mllTaskStack.addFirst(it);
	}
	
	private synchronized IconifiedText popTask() {
		if (null == mllTaskStack) {
			mllTaskStack = new LinkedList<IconifiedText>();
			return null;
		} else {
			if (mllTaskStack.isEmpty())
				return null;
			else
				return mllTaskStack.removeLast();
		}
	}
					
	//[improve] keep aspect ratio
	private Drawable stretchThumbNail(Bitmap org) {
		Bitmap res = stretchBitmap(org, THUMBNAIL_WIDTH, THUMBNAIL_WIDTH, true);
		if (null != res){
			return new BitmapDrawable(res);  
		}else
			return null;
	}
	
	// the functions of handling bitmap
	private Bitmap stretchBitmap(Bitmap org, int width, int height, boolean needKeepAspect){
		if (null == org || 0 >= width || 0 >= height){
			Log.e(TAG, "invalid parameter in stretchBitmap");
			return null;
		}else {
			int w = org.getWidth();
			int h = org.getHeight();

			Matrix matrix = new Matrix();

			float scaleWidth = ((float) width / w);
			float scaleHeight = ((float) height / h);
//		    matrix.postScale(scaleWidth, scaleHeight);
			if (needKeepAspect) {
				float scale = Math.min(scaleWidth, scaleHeight);
				matrix.postScale(scale, scale);				
			}else {
				matrix.postScale(scaleWidth, scaleHeight);
			}
			
			Bitmap newbmp = Bitmap.createBitmap(org, 0, 0, w, h,
					matrix, true);
			return newbmp;
		}
	}
	
	
	// the functions of handling bitmap
	private Bitmap stretchBitmap2(Bitmap org, int width, int height, boolean needKeepAspect){
		if (null == org || 0 >= width || 0 >= height){
			Log.e(TAG, "invalid parameter in stretchBitmap");
			return null;
		}else {
			int w = org.getWidth();
			int h = org.getHeight();

			Matrix matrix = new Matrix();

			float scaleWidth = ((float) width / w);
			float scaleHeight = ((float) height / h);
//		    matrix.postScale(scaleWidth, scaleHeight);
			if (needKeepAspect) {
				float scale = Math.min(scaleWidth, scaleHeight);
				matrix.postScale(scale, scale);				
			}else {
				matrix.postScale(scaleWidth, scaleHeight);
			}
			
			matrix.postRotate(-10);
			
			Bitmap newbmp = Bitmap.createBitmap(org, 0, 0, w, h,
					matrix, true);
			return newbmp;
		}
	}
	
	private Bitmap compositeBitamp(Bitmap src1, Bitmap src2, 
			int x, int y, 
			int width, int height, 
			boolean needKeepAspect){
		if (null == src1 || null == src2 || 0 == width || 0 == height){
			Log.i(TAG, "invalid parameter in compositeBitamp");
			return null;
		}else{
			int w2 = src2.getWidth();
			int h2 = src2.getHeight();
			Bitmap temp = null;
			if (width != w2 || height != h2){
				temp = stretchBitmap(src2, width, height, needKeepAspect);
			}else
				temp = src2;
			
			int w = src1.getWidth();
			int h = src1.getHeight();
			int wt = temp.getWidth();
			int ht = temp.getHeight();
			
			Bitmap bitmap = Bitmap.createBitmap( w, h, Config.ARGB_8888 );
	        Canvas cv = new Canvas( bitmap );
	        
	        cv.drawBitmap( src1, 0, 0, null );
	        cv.drawBitmap( temp,
	        		x + ( width - wt ) / 2,
	        		y + ( height - ht ) / 2,
	        		null );  
	        
	        cv.save( Canvas.ALL_SAVE_FLAG );
	        cv.restore();
	        
	        return bitmap;
		}
	}
	
	
	private String createWhereCondition(File file) {
		if (null == file || !file.isFile() )
			return null;
		else{
			Log.i(TAG, "the file path is " + file.getAbsolutePath()); 
			return String.format("_data='%s'", file.getAbsolutePath());					
		}	
	}
	
	private String createWhereCondition(File [] files) {
		
		if (null == files || files.length == 0 )
			return null;
		
		StringBuffer sb = new StringBuffer(20);
		for (int i = 0; i < files.length; i++){
			if (files[i] == null)
				Log.i(TAG, "file i is null ");
			sb.append(String.format(" _data='%s' ", files[i].getAbsolutePath()));
			if ( files.length != i + 1)
				sb.append("or");
		}
		
		return sb.toString();
	}
	
	private Drawable createThumbNailForImage (File file) {
		return null;
	}

	private boolean onGetThumbNailFile (final IconifiedText it, Drawable icon) {
		if (null == it || null == icon){
			Log.e(TAG, "it is null OR icon is null in onGetThumbNailFile");
			return false;
		} else {
			it.setIcon(icon);
			
			if (null == it.getView()
					|| null == it.getView().getTag(R.string.tag_for_it)						
					|| !it.getView().getTag(R.string.tag_for_it).equals(it))
				Log.i(TAG, "icon view is null or view is not for this it");
			else {
				// update the icon with the handler
				if (null != mhdSendMsg) {
					mhdSendMsg.post(new Runnable(){

						//@Override
						public void run() {
							// TODO Auto-generated method stub
							if (null != it.getView()){
								if (null != it.getView().getTag(R.string.tag_for_it)) {
									if (it.getView().getTag(R.string.tag_for_it).equals(it)) {
										Log.i(TAG, "udpate the icon view in Runnable");
										((ImageView) it.getView().getTag(R.string.tag_for_icon))
												.setImageDrawable(it.getIcon());
										Log.i(TAG, "the icon is "+ it.getIcon().toString());										
//										it.getIconView().invalidate();
										
//										if (it.getIconView().isShown())
//											Log.i(TAG, "the icon is shown");
//										else
//											Log.i(TAG, "the icon is not shown");
									}
								}									
							}else {
								Log.i(TAG, "icon view is null");
							}
						}						
					});					
			}
				return true;
			}					
			return true;
		}
	}
	
	private Drawable createThumbNailForDir(Drawable drawable){
		if (null == drawable){
			Log.e(TAG, "drawable is null in createThumbNailForDir");
			return null;
		}else{
			Bitmap bg =  ((BitmapDrawable)mContext.getResources().
					getDrawable(R.drawable.icon_default_folder)).getBitmap();
			Bitmap content = ((BitmapDrawable)drawable).getBitmap();
			Bitmap comp = compositeBitamp(bg, content, 5, 21, 80, 65, true);
//			Bitmap comp = compositeBitamp(bg, content, 10, 26, 70, 55, true);
			if (null == comp){
				Log.e(TAG, "can not composite bitmap in createThumbNailForDir");
				return null;
			}else{
				return new BitmapDrawable(comp); 
			}
		}		
	}
	
	private Drawable getThumbNailForImage(File file) {

		Cursor cur = null;
		// Log.i(TAG, "the content resolver is " + mContext.getContentResolver().toString());
		// Log.i(TAG, "the uri is " + MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
		// Log.i(TAG, "the condition is " + createWhereCondition(file));

		cur = MediaStore.Images.Media.query(mContext.getContentResolver(),
				MediaStore.Images.Media.EXTERNAL_CONTENT_URI, PROJ_IMAGE,
				createWhereCondition(file),
				MediaStore.Images.Media.DEFAULT_SORT_ORDER);

		if (null == cur || !cur.moveToNext()) {
			Log.i(TAG, "can not find file id in image table");
			cur.close();
			return null;
		} else {
			Log.i(TAG, "get file id in image table");
			Bitmap bmp = null;
			bmp = MediaStore.Images.Thumbnails.getThumbnail(mContext
					.getContentResolver(), cur.getInt(cur
					.getColumnIndex(BaseColumns._ID)),
					Images.Thumbnails.MINI_KIND, options);

			if (null == bmp) {
				Log.i(TAG, "can not get icon with getThumbnail");
				cur.close();
				return null;
			} else {
				Log.i(TAG, "get Thumbnail");
				cur.close();
				return stretchThumbNail(bmp);
			}			
		}
	}
	
	private Drawable getThumbNailForVideo(File file) {
		Cursor cur = null;
		cur = mContext.getContentResolver().query(
				MediaStore.Video.Media.EXTERNAL_CONTENT_URI, PROJ_VIDEO,
				createWhereCondition(file), null, null);

		if (null == cur || !cur.moveToNext()) {
			Log.i(TAG, "can not find file id in video table");
			cur.close();
			return null;
		} else {
			long id = cur.getLong(cur.getColumnIndex(BaseColumns._ID));
			Log.i(TAG, "the id of video is " + String.valueOf(id));

			Bitmap bmp = null;
			bmp = MediaStore.Video.Thumbnails.getThumbnail(
					mContext.getContentResolver(), id, 
					Images.Thumbnails.MINI_KIND,
					options);

			if (null == bmp) {
				Log.i(TAG, "can not get icon with getThumbnail");
				cur.close();
				return null;
			} else {
				Log.i(TAG, "get Thumbnail");
				cur.close();
				return stretchThumbNail(bmp);
			}
		}
	}
	
	private Drawable getThumbNailForFile(File file) {
		if (null == file || !file.exists() || !file.isFile()){
			Log.i(TAG, "file is invalid in getThumbNailForFile");
			return null;
		}else{
			if(FileUtil.isImageFile(mContext, file)){
				return getThumbNailForImage(file);
			}else if (FileUtil.isVideoFile(mContext, file)){
				return getThumbNailForVideo(file);
			}else{
				Log.i(TAG, "not support file type");
				return null;
			}
		}
	}
	
	private Drawable getThumbNailForDir(File dir) {
		if (null == dir || !dir.exists() || !dir.isDirectory()){
			Log.i(TAG, "file is invalid in getThumbNailForDir");
			return null;
		}else{
			Drawable drawable = null;
			File[] files = dir.listFiles();

			for (File file : files){
				if (FileUtil.isImageFile(mContext, file)){
					drawable = getThumbNailForImage(file);
				}else if (FileUtil.isVideoFile(mContext, file)){
					drawable = getThumbNailForVideo(file);
				}
				
				if (null != drawable)
					break;
			}
			
			if (null != drawable){
				drawable = createThumbNailForDir(drawable);
			}
			
			return drawable;
		}		
	}
	
	
	@Override
	public void preRun() {
		// TODO Auto-generated method stub
		super.preRun();
		options.inSampleSize = 3;
		options.outHeight = THUMBNAIL_WIDTH;
		options.outWidth = THUMBNAIL_HEIGHT;
	}
	

	@Override
	protected void realRun() {
		// TODO Auto-generated method stub
		super.realRun();
		

		
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inSampleSize = 3;
		options.outHeight = THUMBNAIL_WIDTH;
		options.outWidth = THUMBNAIL_HEIGHT;

		while (!isStop()) {						
			// add new tasks in stack
			if (mbHaveNewTasks) {
				mbHaveNewTasks = false;

				for (IconifiedText it : mlitNewTask)
					pushTask(it);
			}
			
			if (mllTaskStack.isEmpty()){
				
				// handler failed task when no task in stack
				if (mllFailedTasks.isEmpty()) {
					try {
						Thread.sleep(1000);
//						Log.i(TAG, "sleep 1 second");
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				} else {
					IconifiedText it = mllFailedTasks.removeFirst();
					if (null == it) {
						Log.e(TAG, "the task popped in failedtasks is null in thread.run");
					}else {
						File file = it.getFile();
						if (null == file || !file.exists()) {
							Log.e(TAG, "the file in task is null or nonexistent in thread.run" );
						} else {
							Drawable icon;								
							icon = createThumbNailForImage(file); // only for image
							if (null != icon){
								mmapCache.put(file, icon);
							}
						}
					}				
				}
			} else {
				// handle the task in stack
				IconifiedText it = popTask();
				
				if (null == it) {
					Log.e(TAG, "the task popped is null in thread.run");
				}else {
					File file = it.getFile();
					if (null == file || !file.exists()) {
						Log.e(TAG, "the file in task is null or nonexistent in thread.run" );
					} else {
						Drawable icon = null;
						
						icon = mmapCache.get(file);
						
						if (null != icon) {
							Log.i(TAG, "get icon from cache for file " + file.getAbsolutePath());
							it.setIcon(icon);
							onGetThumbNailFile(it, icon);
						} else {
							if (file.isFile()){
								icon = getThumbNailForFile(file);
							}else if (file.isDirectory()){
								icon = getThumbNailForDir(file);
							}else{
								Log.i(TAG, "unknown file type in thread.run");
							}
							
							if (null != icon){
								mmapCache.put(file, icon);
								onGetThumbNailFile(it, icon);
							}
							
							try {
								Thread.sleep(50);
//								Log.i(TAG, "sleep 1 second");
							} catch (InterruptedException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							}							
																
						}
					}
				}
			}
		}//while
		
		Log.i(TAG, "the thread stop now");
		// clean the source
		mhdSendMsg = null;
		mllTaskStack.clear();
		mlitNewTask = null;			
		
	}	
}